package com.zjpavt.client.command.offlinecommand;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;

import com.zjpavt.client.Connection;
import com.zjpavt.client.command.entity.VideoServer;
import com.zjpavt.client.enums.ExtendDeviceEnum;
import com.zjpavt.client.extenddevice.voltagecurrent.VideoDevice;
import com.zjpavt.client.hal.ExtendDeviceManager;
import com.zjpavt.client.util.ClientConfig;
import com.zjpavt.client.util.FileUtil;
import com.zjpavt.client.util.LoggerUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * 离线指令统一管理类
 *
 * @author cjp
 */
@Slf4j
public class OfflineCommandManager {

    private static final String OFFLINE_COMMAND_DEFAULT_POSTFIX = ".bin";
    /**
     * 一次请求2年
     */
    private static final int REQUEST_OFFLINE_COMMAND_AMOUNT = 24;
    private static final long WEEK_TIME_MILLSEC = 7 * 86400 * 1000;
    /**
     * 用于校验离线指令   认为4-200位的指令是有效的
     */
    private static final Pattern PATTERN = Pattern.compile("^([0-9]{8})([0-9]{5}):([0-9,a-f]{1,50})$");
    private static final ReentrantLock LOCK = new ReentrantLock();
    private int indexOfCommandList = 1;

    /**
     * 保存离线指令到文件，此处command已经经过正则校验
     *
     * @param command 用于保存的离线指令
     */
    public static void saveOfflineCommand(String command) throws Exception {
        if (command == null || command.isEmpty()) {
            return;
        }
        Map<String, List<String>> commandMapByMonth = new HashMap<>();
        String[] commandGroup = command.substring(1, command.length() - 1).split("]\\[");
        for (int j = 0; j < commandGroup.length; j++) {
            String dateStr = commandGroup[j].substring(0, 6);
            List<String> monthLyCommandList = commandMapByMonth.get(dateStr);
            if (monthLyCommandList == null) {
                monthLyCommandList = new ArrayList<>();
            }
            monthLyCommandList.add(commandGroup[j]);
            commandMapByMonth.put(commandGroup[j].substring(0, 6), monthLyCommandList);
        }
        LOCK.lock();
        try {
            for (String date : commandMapByMonth.keySet()) {
                writeCommandToFile(date, commandMapByMonth.get(date));
            }
        }
        finally {
            LOCK.unlock();
        }
    }

    /**
     * 写入某个月的离线指令
     *
     * @param date 根据日期去获取（创建）文件
     * @param list 一个月的离线指令列表
     */
    private static void writeCommandToFile(String date, List<String> list) throws IOException {
        String filePostFix = OFFLINE_COMMAND_DEFAULT_POSTFIX;
        File dir = new File(ClientConfig.OFFLINE_COMMAND_SAVE_PATH);
        if (dir.exists()) {//创建目录
            if (!dir.isDirectory()) {
                dir.delete();
                dir.mkdirs();
            }
        } else {
            dir.mkdirs();
        }
        File file = new File(ClientConfig.OFFLINE_COMMAND_SAVE_PATH + date + filePostFix);
        if (!file.exists()) {
            file.createNewFile();
        }
        StringBuilder sb = new StringBuilder();
        for (String s : list) {
            sb.append(s).append("\n");
        }
        FileUtil.writeToFile(file.getAbsolutePath(), sb.toString());

    }

    /**
     * 读取某一日的离线指令列表
     * 返回list不为null
     *
     * @return 读取到的某一天的离线指令
     */
    public static List<OfflineCommandBean> readOfflineCommandListByDay(Date date) throws IOException, ParseException {
        List<OfflineCommandBean> offlineCommandBeans = readOfflineCommandListByMonth(date);
        List<OfflineCommandBean> res = new ArrayList<>();
        for (OfflineCommandBean bean : offlineCommandBeans) {
            if (isSameDay(bean.getDate(), date)) {
                res.add(bean);
            }
        }
        return res;
    }

    /**
     * 读取某一月的离线指令列表
     * 返回list不为null
     *
     * @return 离线指令列表
     */
    public static List<OfflineCommandBean> readOfflineCommandListByMonth(Date date) throws IOException, ParseException {
        LOCK.lock();
        try {
            List<OfflineCommandBean> offlineCommandBeans = new ArrayList<>();
            SimpleDateFormat fileNameFormat = new SimpleDateFormat("yyyyMM");
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            String fileName = fileNameFormat.format(date);
            File file = new File(ClientConfig.OFFLINE_COMMAND_SAVE_PATH + fileName + OFFLINE_COMMAND_DEFAULT_POSTFIX);
            if (!file.exists()) {
                return offlineCommandBeans;
            }
            List<String> offlineCommand = FileUtils.readLines(file, ClientConfig.DEFAULT_ENCODING);
            for (String s : offlineCommand) {
                if (s != null && !s.isEmpty()) {
                    Matcher matcher;
                    matcher = PATTERN.matcher(s);
                    if (matcher.matches()) {
                        int second = Integer.parseInt(matcher.group(2));
                        Date date1 = dateFormat.parse(matcher.group(1));
                        date1.setTime(date1.getTime() + second * 1000);
                        offlineCommandBeans.add(new OfflineCommandBean(date1, matcher.group(3)));
                    }
                }
            }
            return offlineCommandBeans;
        }
        finally {
            LOCK.unlock();
        }

    }

    /**
     * 读取所有离线指令列表
     * 返回list不为null
     *
     * @return 离线指令列表
     */
    public static List<OfflineCommandBean> readOfflineCommandListByYear(Date date) throws IOException, ParseException {
        LOCK.lock();
        try {
            List<OfflineCommandBean> offlineCommandBeans = new ArrayList<>();
            SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
            SimpleDateFormat monthFormat = new SimpleDateFormat("MM");
            SimpleDateFormat fileNameFormat = new SimpleDateFormat("yyyyMM");
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            Integer year = Integer.valueOf(yearFormat.format(date));
            Integer month = Integer.valueOf(monthFormat.format(date));
            String fileName = fileNameFormat.format(date);
            File file = new File(ClientConfig.OFFLINE_COMMAND_SAVE_PATH + fileName + OFFLINE_COMMAND_DEFAULT_POSTFIX);
            do {
                List<String> offlineCommand = FileUtils.readLines(file, ClientConfig.DEFAULT_ENCODING);
                for (String s : offlineCommand) {
                    if (s != null && !s.isEmpty()) {
                        Matcher matcher;
                        matcher = PATTERN.matcher(s);
                        if (matcher.matches()) {
                            int second = Integer.parseInt(matcher.group(2));
                            Date date1 = dateFormat.parse(matcher.group(1));
                            date1.setTime(date1.getTime() + second * 1000);
                            offlineCommandBeans.add(new OfflineCommandBean(date1, matcher.group(3)));
                        }
                    }
                }
                String monthStr = "";
                if(month==12) {
                    year++;
                    month=1;
                } else {
                    month++;
                }
                monthStr = month.toString();
                if(monthStr.length()==1)
                    monthStr = "0"+monthStr;
                fileName = year.toString() + monthStr;
                file = new File(ClientConfig.OFFLINE_COMMAND_SAVE_PATH + fileName + OFFLINE_COMMAND_DEFAULT_POSTFIX);
            } while(file.exists());
            return offlineCommandBeans;
        }
        finally {
            LOCK.unlock();
        }

    }

    /**
     * 获取当前正在执行的离线指令！
     *
     * @return 某个时间点获取到的离线指令（获取不到返回null）
     */
    public static OfflineCommandBean readOfflineCommandByTime(Date date) throws IOException, ParseException {
        //按照时间对当日指令升序排序
        List<OfflineCommandBean> list = orderOfflineCommandListByTime(readOfflineCommandListByDay(date));
        //当日没有离线指令时 取前七天的最后一条离线指令
        if (list.isEmpty()) {
            return readLastOfflineCommandOfWeek(date);
        }
        for (int j = list.size() - 1; j >= 0; j--) {
            if (list.get(j).getDate().getTime() < date.getTime()) {
                return list.get(j);
            }
        }
        //当日最早指令晚于需要时间时，取前七天最后一条离线指令
        return readLastOfflineCommandOfWeek(date);
    }


    /**
     * 获取已执行过的离线指令,index 指向前找第几条
     * @param index 从0开始
     * @param date
     * @return 某个时间点获取到的离线指令（获取不到返回null）
     */
    public static OfflineCommandBean readPreOfflineCommandByTime(Date date, int index) throws IOException, ParseException {
        //按照时间对当日指令升序排序
//		List<OfflineCommandBean> list = orderOfflineCommandListByTime(readOfflineCommandListByDay(date));
        List<OfflineCommandBean> listThisMonth = orderOfflineCommandListByTime(readOfflineCommandListByMonth(date));
        //当日没有离线指令时 取前七天的最后一条离线指令
        if (listThisMonth.isEmpty()) {
            return null;
//			return readLastOfflineCommandOfWeek(date);
        }
        for (int j = listThisMonth.size() - 1; j >= 0; j--) {
            if (listThisMonth.get(j).getDate().getTime() < date.getTime()) {
                int preIndex = j - ((index) % (j + 1));
                return listThisMonth.get(preIndex);
            }
        }
        //当日最早指令晚于需要时间时，取前七天最后一条离线指令
        return readLastOfflineCommandOfWeek(date);
    }

    /**
     * 返回大于等于当月的指令
     * @param date
     */
    public static List<OfflineCommandBean> offlineCommandByList(Date date) throws IOException, ParseException {
        //按照时间对当日指令升序排序
        List<OfflineCommandBean> listThisMonth = orderOfflineCommandListByTime(readOfflineCommandListByYear(date));
        if (listThisMonth.isEmpty()) {
            return null;
        }
        return listThisMonth;
    }

    /**
     * @return 读取某天的最后一个离线指令
     */
    private static OfflineCommandBean readLastOfflineCommandOfDay(Date date) throws IOException, ParseException {
        //按照时间对当日指令升序排序
        List<OfflineCommandBean> list = orderOfflineCommandListByTime(readOfflineCommandListByDay(date));
        if (list.isEmpty()) {
            return null;
        }
        return list.get(list.size() - 1);
    }

    /**
     * @param date 定位离线指令的时间
     * @return 读取下一个离线指令，仅用于显示，所以两年
     */
    public static OfflineCommandBean readNextOfflineCommand(Date date) throws IOException, ParseException {
        List<OfflineCommandBean> listThisMonth = orderOfflineCommandListByTime(readOfflineCommandListByMonth(date));
        if (!listThisMonth.isEmpty()) {
            for (OfflineCommandBean bean : listThisMonth) {
                if (bean.getDate().getTime() > date.getTime()) {
                    return bean;
                }
            }
        }
        //当月找不到下个离线指令时
        int maxSearchMonth = 12;
        for (int j = 1; j <= maxSearchMonth; j++) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MONTH, j);
            List<OfflineCommandBean> offlineCommandBeansByMonth = readOfflineCommandListByMonth(calendar.getTime());
            if (!offlineCommandBeansByMonth.isEmpty()) {
                return offlineCommandBeansByMonth.get(0);
            }
        }
        return null;
    }

    /**
     * @param date  定位离线指令的时间
     * @param index 第几条离线指令 从0开始
     * @return 读取下 @index 个离线指令，仅用于显示，所以两年
     * <p>
     * 往后默认是读的当月
     */
    public static OfflineCommandBean readNextOfflineCommand(Date date, int index) throws IOException, ParseException {
        List<OfflineCommandBean> listThisMonth = orderOfflineCommandListByTime(readOfflineCommandListByMonth(date));
        if (!listThisMonth.isEmpty()) {
            for (int i = 0; i < listThisMonth.size(); i++) {
                if (listThisMonth.get(i).getDate().getTime() > date.getTime()) {
//					index算法解释： i为当前最近一条记录 size-i 为当前可选的离线指令数量 index是可选中的序号
                    int realIndexInList = i + ((index) % (listThisMonth.size() - i));
//					if (realIndexInList<0 || realIndexInList>listThisMonth.size()) {
//						return listThisMonth.get(listThisMonth.size() - i);
//					}
                    return listThisMonth.get(realIndexInList);
                }

            }
        } else {
//			当月找不到指令的时候 继续往下执行
        }
        //当月找不到下个离线指令时
        int maxSearchMonth = 12;
        for (int j = 1; j <= maxSearchMonth; j++) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MONTH, j);
            List<OfflineCommandBean> offlineCommandNextMonth = readOfflineCommandListByMonth(calendar.getTime());
            if (!offlineCommandNextMonth.isEmpty()) {
                int realIndexInList = (index - 1) % offlineCommandNextMonth.size();
                return offlineCommandNextMonth.get(realIndexInList);
            }
        }
        return null;
    }

    /**
     * 读取前7天的最后一个离线指令
     *
     * @return 读取到的离线指令
     */
    private static OfflineCommandBean readLastOfflineCommandOfWeek(Date date) throws IOException, ParseException {
        //1.读取当月的离线指令 排序后查找上一条
        List<OfflineCommandBean> offlineCommandBeanOfMonth = orderOfflineCommandListByTime(readOfflineCommandListByMonth(date));
        if (!offlineCommandBeanOfMonth.isEmpty()) {
            for (int i = offlineCommandBeanOfMonth.size() - 1; i >= 0; i--) {
                if (offlineCommandBeanOfMonth.get(i).getDate().getTime() < date.getTime()) {
                    return offlineCommandBeanOfMonth.get(i);
                }
            }
        }
        //2.当月找不到，且当前日期小于7,则找到上个月的最后一条离线指令，若时间差小于7天则返回
        if (Calendar.getInstance().get(Calendar.DAY_OF_MONTH) < 7) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            calendar.add(Calendar.MONTH, -1);
            List<OfflineCommandBean> offlineCmdListOfLastMonth = orderOfflineCommandListByTime(readOfflineCommandListByMonth(calendar.getTime()));
            int size = offlineCmdListOfLastMonth.size();
            if (size != 0 && (date.getTime() - offlineCmdListOfLastMonth.get(size - 1).getDate().getTime()) < WEEK_TIME_MILLSEC) {
                return offlineCmdListOfLastMonth.get(size - 1);
            }
        }
        return null;
    }


    /**
     * @return 按照时间升序排序OfflineCommandBeanList
     */
    private static List<OfflineCommandBean> orderOfflineCommandListByTime(List<OfflineCommandBean> list) {
        if (list.isEmpty()) {
            return list;
        }
        Collections.sort(list);
        return list;
    }

    /**
     * @return 是否成功删除存储的所有离线指令
     */
    public static boolean deleteAllOfflineCommand() {

        File dir = new File(ClientConfig.OFFLINE_COMMAND_SAVE_PATH);
        LOCK.lock();
        try {
            FileUtils.deleteDirectory(dir);
        } catch (IOException e) {
            log.error(LoggerUtil.convertException2String(e));
            return false;
        }
        finally {
            LOCK.unlock();
        }
        return true;
    }

    /**
     * 接收到A指令
     */
    public static synchronized void aCommandReceive() {
        if (waitForACommand != null) {
            log.info("count down " + waitForACommand.getCount());
            waitForACommand.countDown();
        }
    }

    public static CountDownLatch waitForACommand= new CountDownLatch(1);


    /**
     * 请求两年的离线指令
     * 通过CountDownLatch 实现接收到A指令后再发送B指令
     */
    public static void requestForOfflineCommand(Connection conn) {
        String prefix = "<B_";
        String postFix = ">";
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
        for (int j = 0; j < REQUEST_OFFLINE_COMMAND_AMOUNT; j++) {
            if (j != 0) {
                synchronized (OfflineCommandManager.class) {
                    log.info("wait for A command");
                    waitForACommand = new CountDownLatch(1);
                }
                try {
                    if (waitForACommand.getCount() != 0) {
                        log.info("begin await a command " + waitForACommand.getCount());
                        waitForACommand.await(15000,TimeUnit.MILLISECONDS);
                    }
                } catch (InterruptedException e) {
                    log.error(LoggerUtil.convertException2String(e));
                } catch (Exception e1) {
                    log.error(LoggerUtil.convertException2String(e1));
                }
            }
            StringBuilder commandBuilder = new StringBuilder();
            commandBuilder.append(prefix)
            .append(dateFormat.format(calendar.getTime()))
            .append(postFix);
            conn.appendMsgToQueue(commandBuilder.toString());
            calendar.add(Calendar.MONTH, 1);
        }
        //视频播放调节节目
        VideoDevice videoDevice = (VideoDevice)ExtendDeviceManager.getInstance().getExtendDevice(ExtendDeviceEnum.VIDEO);
        if(videoDevice != null)
            videoDevice.transformationPlay(VideoServer.getInstance());
    }

    private static boolean isSameDay(Date date1, Date date2) {
        if (date1 != null && date2 != null) {
            Calendar cal1 = Calendar.getInstance();
            cal1.setTime(date1);
            Calendar cal2 = Calendar.getInstance();
            cal2.setTime(date2);
            return isSameDay(cal1, cal2);
        } else {
            throw new IllegalArgumentException("The date must not be null");
        }
    }

    private static boolean isSameDay(Calendar cal1, Calendar cal2) {
        if (cal1 != null && cal2 != null) {
            return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
        } else {
            throw new IllegalArgumentException("The date must not be null");
        }
    }

}
